Skip to content

Write item name and quantity changes to an edited dfs file - #79

Draft
ryan-kipawa wants to merge 3 commits into
masterfrom
fix/38
Draft

ryan-kipawa wants to merge 3 commits into
masterfrom
fix/38

Conversation

@ryan-kipawa

@ryan-kipawa ryan-kipawa commented Oct 13, 2026 •

Copy link
Copy Markdown
Collaborator

Closes #38

What and why

In a dfs file opened for editing, assigning ItemInfo[0].Name or ItemInfo[0].Quantity changed only the Python object, and closing the file wrote nothing. Fixing a wrong eumItem/eumUnit on a 100 GB file meant rewriting the file or using DataUtility. The C# library writes these item edits; the Python port never did.

The file now writes these edits on Flush or Close. When a file is opened in Edit or Append mode, it remembers the dynamic items it read and the Quantity object each was read with. On Flush or Close it writes, through dfsUpdateItemInfo, every item whose Name changed or that was assigned a new Quantity. Only the header changes; the data is never rewritten. The example from the issue (ExamplesDfs2.ModifyDfs2ItemInfo) now works unchanged.

f = DfsFileFactory.Dfs2FileOpenEdit(filename)
f.ItemInfo[0].Name = "GroundUse"  # stored as "GroundU", the length of "Landuse"
f.ItemInfo[0].Quantity = eumQuantity(eumItem.eumIAreaFraction, eumUnit.eumUPerCent)
f.Close()  # writes both

The C# library behaves the same way in these respects:

  • A new name is truncated or padded with spaces to the stored length, because the native library keeps the name's length in the file.
  • Changing Quantity.Item or Quantity.Unit in place is not written; only assigning a new Quantity is.

Errors:

  • A name cp1252 cannot encode raises UnicodeEncodeError.
  • A name containing NUL raises ValueError.
  • A unit that is not a unit of its item (checked with eumItemUnitEqv) raises ValueError.

Every change is checked before any is written, so a rejected value leaves the file unchanged, and Close still closes the file when it raises.

Redesign. Earlier versions of this PR ported the C# change events: the setters and a __setattr__ override on axes wrote on assignment, and axis editing was included. That needed weak callbacks, rollback, copy handling and detaching items on Close, and most review rounds fixed a new aliasing or lifetime bug in that machinery. An adversarial design review found one more: a copy.copy of an item still wrote to the file. This version replaces all of that with one write at Flush/Close, and cuts the scope to what the issue asked for. The history was rebuilt from master; the old commits are still visible in this PR's timeline.

Side finding: DfsuFile.Flush() raised TypeError (it passed itself to DfsFile.Flush), and DfsuFileOpenAppend was the only Open method that required parameters.

Commits

  1. da1afa6 Test writing item name and quantity edits to a dfs file. This ports the commented-out C# test that runs the issue's example (test_ModifyLanduseItemInfoTest) and adds tests/test_dfs_edit.py. Fails on master.
  2. b997d60 Fix DfsuFile.Flush and let DfsuFileOpenAppend default its parameters.
  3. 237d24f Write item name and quantity edits on Flush and Close, with a changelog entry.

Changes

  • mikecore/DfsFile.py:
    • Open records the dynamic items of an Edit/Append file.
    • The new private __WriteItemInfo checks every change, then writes the changes.
    • Flush calls it.
    • Close calls it in a try/finally and then forgets the items, because their pointers point into the freed header.
    • New DfsDLLUtil.GetItemNameBytes reads the stored name bytes. A name that cp1252 cannot decode is written back unchanged when only the Quantity changes.
  • mikecore/DfsDLL.py: ctypes signature of dfsUpdateItemInfo.
  • mikecore/DfsuFile.py, mikecore/DfsFileFactory.py: the side finding.

Not changed (on purpose)

  • Spatial axes (item axes and Dfs123File.SpatialAxis): not written, as on master. The issue did not ask for this. test_ModifyAxisTest stays commented out.
  • Static items: a changed name or quantity is not written, as on master.
  • Files being built with DfsBuilder: item edits are not written.
  • Read mode: edits change only the Python objects, as on master.
  • Projection, FileInfo and the other item fields (reference coordinates, orientation and so on): not written.

Things to know

  • Append mode: the native dfsFileFlush does not write the header, so an edit reaches the file only on Close. This also applies to the time step count, and was already the case. Edit mode writes on Flush. Both docstrings and the changelog say so.
  • Errors come late: a rejected value raises on Flush/Close, not on assignment. Call Flush to check before Close.
  • Native failure partway through: the up-front checks cover every error I could produce, but if dfsUpdateItemInfo itself failed partway through, the items before it would be written.
  • Not tested:
    • a file dropped without Close writes its edits when __del__ runs;
    • the Edit-mode Flush test reads the file's raw bytes while it is open, which is not yet tested on Windows (CI will be the first run).

Evidence

Each claim is checked by a test that reads the file back after the edit:

Claim Test
The issue's example writes the name and quantity tests/test_dfs2.py::Dfs2Tests::test_ModifyLanduseItemInfoTest
A name or quantity edit reaches that item only, and all data reads back unchanged; on dfs0/1/2/3/dfsu, in Edit, Append and generic Edit mode, for each item test_name_edit_is_written_to_that_item_only, test_quantity_edit_is_written_to_that_item_only (15 cases each)
A new name is padded or truncated to the stored length, at the name's offset in the file, and the item shows the stored name test_name_keeps_its_length_in_the_file (25)
A rejected name or unit on one item raises on Flush and Close, the valid edit of another item is not written either, the file is byte-identical and is closed test_rejected_value_changes_no_item (30)
Nothing is written in Read mode, after Close, for an in-place Quantity change, or for an item put into ItemInfo test_read_mode_writes_nothing, test_edit_after_close_writes_nothing, test_quantity_changed_in_place_is_not_written, test_item_put_in_item_info_is_not_written
Flush writes in Edit mode, and an append after Flush lands at the end test_flush_writes_the_edit_in_edit_mode, test_flush_then_append
A Quantity edit keeps a name cp1252 cannot decode byte-identical test_quantity_edit_keeps_a_name_cp1252_cannot_decode

I checked that the tests catch a broken implementation by breaking one part at a time and running tests/test_dfs_edit.py:

Broken on purpose Tests that failed
No unit check before writing test_rejected_value_changes_no_item
No try/finally in Close test_rejected_value_changes_no_item
Loop over the public ItemInfo instead of the items read Python crashes with a segmentation fault (writes through freed item pointers), so the suite fails
Name not padded or truncated to the stored length test_name_keeps_its_length_in_the_file
Write every item whether changed or not test_quantity_changed_in_place_is_not_written
Flush doesn't write test_flush_writes_the_edit_in_edit_mode, test_rejected_value_changes_no_item

Full suite at each commit (uv run pytest):

da1afa6  102 failed, 202 passed, 1 skipped, 8 xfailed   (new tests, master code)
b997d60  101 failed, 203 passed, 1 skipped, 8 xfailed   (dfsu fix)
237d24f  304 passed, 1 skipped, 8 xfailed

At 237d24f:

  • uvx pre-commit run --all-files: all passed.
  • uv run pyrefly check: 0 errors, 328 warnings, the same as master; the CI cap is 330.

🤖 Generated with Claude Code

@ryan-kipawa ryan-kipawa self-assigned this Oct 13, 2026
@ryan-kipawa ryan-kipawa changed the title Write item name, quantity and axis changes to an edited dfs file Write item name and quantity changes to an edited dfs file Oct 20, 2026
ryan-kipawa and others added 3 commits October 20, 2026 17:09
Port the commented-out C# test of ExamplesDfs2.ModifyDfs2ItemInfo, the
example from issue #38, and test name and quantity edits on every dfs
file type and way of opening it for editing. They fail on master, which
never writes item info.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
DfsuFile.Flush passed itself to DfsFile.Flush, which takes no argument,
so it raised TypeError. DfsuFileOpenAppend was the only Open method that
required parameters.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
In a file opened for editing, the file remembers the dynamic items it
read. Flush and Close write the ones whose Name changed or that were
assigned a new Quantity, through dfsUpdateItemInfo. As in the C#
library, a new name is truncated or padded to the stored length, and a
Quantity changed in place is not written. Every change is checked
before any is written, and Close still closes the file when it raises.

Closes #38

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Modify dfs2 items not working

1 participant